﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DeviceInstallation.cs" company="Matthias Friedrich">
//   Copyright © Matthias Friedrich 2009
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace DeviceManagement
{
	using System;
	using System.Collections.Generic;
	using System.Drawing;
	using System.Runtime.InteropServices;
	using System.Text;
	using System.Windows.Forms;

	[StructLayout(LayoutKind.Sequential)]
	internal struct SP_DEVINFO_DATA
	{
		public uint cbSize;

		public Guid ClassGuid;

		public uint DevInst;

		public IntPtr Reserved;
	}

	[StructLayout(LayoutKind.Sequential)]
	internal struct SP_DEVICE_INTERFACE_DATA
	{
		public uint cbSize;

		public Guid InterfaceClassGuid;

		public uint Flags;

		public IntPtr Reserved;
	}

	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
	internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
	{
		public uint cbSize;

		[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
		public string DevicePath; // short pointer

		public static SP_DEVICE_INTERFACE_DETAIL_DATA Empty = new SP_DEVICE_INTERFACE_DETAIL_DATA();
	}

	[StructLayout(LayoutKind.Sequential)]
	internal struct DEVPROPKEY
	{
		/// <summary>
		///     A GUID value that specifies a property category.
		/// </summary>
		public Guid fmtid;

		/// <summary>
		///     An unsigned long value that uniquely identifies the property within the property category. For internal system reasons, a property identifier must be greater than or equal to two.
		/// </summary>
		public uint pid;
	};

	[StructLayout(LayoutKind.Sequential)]
	internal struct SP_CLASSIMAGELIST_DATA
	{
		public uint cbSize;

		public IntPtr ImageList;

		public UIntPtr Reserved;
	}

	[Flags()]
	public enum DeviceCaps : uint
	{
		LockSupported = DeviceInstallation.CM_DEVCAP_LOCKSUPPORTED, 

		EjectSupported = DeviceInstallation.CM_DEVCAP_EJECTSUPPORTED, 

		Removable = DeviceInstallation.CM_DEVCAP_REMOVABLE, 

		DockDevice = DeviceInstallation.CM_DEVCAP_DOCKDEVICE, 

		UniqueId = DeviceInstallation.CM_DEVCAP_UNIQUEID, 

		SilentInstall = DeviceInstallation.CM_DEVCAP_SILENTINSTALL, 

		RawDeviceOk = DeviceInstallation.CM_DEVCAP_RAWDEVICEOK, 

		SuppriseRemovalOk = DeviceInstallation.CM_DEVCAP_SURPRISEREMOVALOK, 

		HardwareDisabled = DeviceInstallation.CM_DEVCAP_HARDWAREDISABLED, 

		NonDynamic = DeviceInstallation.CM_DEVCAP_NONDYNAMIC
	}

	internal sealed class DeviceInstallation
	{
		internal const ulong INVALID_HANDLE_VALUE = 0xFFFFFFFFFFFFFFFF;

		internal const uint DICLASSPROP_INSTALLER = 0x00000001;

		internal const uint DICLASSPROP_INTERFACE = 0x00000002;

		internal const uint DIGCF_PRESENT = 0x00000002;

		internal const uint DIGCF_ALLCLASSES = 0x00000004;

		internal const uint DIGCF_DEVICEINTERFACE = 0x00000010;

		internal const uint APPLICATION_ERROR_MASK = 0x20000000;

		internal const uint ERROR_SEVERITY_ERROR = 0xC0000000;

		internal const uint ERROR_NO_DEVICE_SELECTED = APPLICATION_ERROR_MASK | ERROR_SEVERITY_ERROR | 0x211;

		internal const uint ERROR_NO_MORE_ITEMS = 259;

		internal const uint SPINT_ACTIVE = 0x00000001;

		internal const uint SPINT_DEFAULT = 0x00000002;

		internal const uint SPINT_REMOVED = 0x00000004;

		internal const uint DEVPROP_TYPEMOD_ARRAY = 0x00001000;

		internal const uint DEVPROP_TYPEMOD_LIST = 0x00002000;

		internal const uint DEVPROP_TYPE_EMPTY = 0x00000000;

		internal const uint DEVPROP_TYPE_NULL = 0x00000001;

		internal const uint CM_DEVCAP_LOCKSUPPORTED = 0x00000001;

		internal const uint CM_DEVCAP_EJECTSUPPORTED = 0x00000002;

		internal const uint CM_DEVCAP_REMOVABLE = 0x00000004;

		internal const uint CM_DEVCAP_DOCKDEVICE = 0x00000008;

		internal const uint CM_DEVCAP_UNIQUEID = 0x00000010;

		internal const uint CM_DEVCAP_SILENTINSTALL = 0x00000020;

		internal const uint CM_DEVCAP_RAWDEVICEOK = 0x00000040;

		internal const uint CM_DEVCAP_SURPRISEREMOVALOK = 0x00000080;

		internal const uint CM_DEVCAP_HARDWAREDISABLED = 0x00000100;

		internal const uint CM_DEVCAP_NONDYNAMIC = 0x00000200;

		/// <summary>
		///     8-bit signed int (SBYTE)
		/// </summary>
		internal const uint DEVPROP_TYPE_SBYTE = 0x00000002;

		/// <summary>
		///     8-bit unsigned int (BYTE)
		/// </summary>
		internal const uint DEVPROP_TYPE_BYTE = 0x00000003;

		/// <summary>
		///     16-bit signed int (SHORT)
		/// </summary>
		internal const uint DEVPROP_TYPE_INT16 = 0x00000004;

		/// <summary>
		///     16-bit unsigned int (USHORT)
		/// </summary>
		internal const uint DEVPROP_TYPE_UINT16 = 0x00000005;

		/// <summary>
		///     32-bit signed int (LONG)
		/// </summary>
		internal const uint DEVPROP_TYPE_INT32 = 0x00000006;

		/// <summary>
		///     32-bit unsigned int (ULONG)
		/// </summary>
		internal const uint DEVPROP_TYPE_UINT32 = 0x00000007;

		/// <summary>
		///     64-bit signed int (LONG64)
		/// </summary>
		internal const uint DEVPROP_TYPE_INT64 = 0x00000008;

		/// <summary>
		///     64-bit unsigned int (ULONG64)
		/// </summary>
		internal const uint DEVPROP_TYPE_UINT64 = 0x00000009;

		/// <summary>
		///     32-bit floating-point (FLOAT)
		/// </summary>
		internal const uint DEVPROP_TYPE_FLOAT = 0x0000000A;

		/// <summary>
		///     64-bit floating-point (DOUBLE)
		/// </summary>
		internal const uint DEVPROP_TYPE_DOUBLE = 0x0000000B;

		/// <summary>
		///     128-bit data (DECIMAL)
		/// </summary>
		internal const uint DEVPROP_TYPE_DECIMAL = 0x0000000C;

		/// <summary>
		///     128-bit unique identifier (GUID)
		/// </summary>
		internal const uint DEVPROP_TYPE_GUID = 0x0000000D;

		/// <summary>
		///     64 bit signed int currency value (CURRENCY)
		/// </summary>
		internal const uint DEVPROP_TYPE_CURRENCY = 0x0000000E;

		/// <summary>
		///     date (DATE)
		/// </summary>
		internal const uint DEVPROP_TYPE_DATE = 0x0000000F;

		/// <summary>
		///     file time (FILETIME)
		/// </summary>
		internal const uint DEVPROP_TYPE_FILETIME = 0x00000010;

		/// <summary>
		///     8-bit boolean (DEVPROP_BOOLEAN)
		/// </summary>
		internal const uint DEVPROP_TYPE_BOOLEAN = 0x00000011;

		/// <summary>
		///     null-terminated string
		/// </summary>
		internal const uint DEVPROP_TYPE_STRING = 0x00000012;

		/// <summary>
		///     multi-sz string list
		/// </summary>
		internal const uint DEVPROP_TYPE_STRING_LIST = DEVPROP_TYPE_STRING | DEVPROP_TYPEMOD_LIST;

		internal const uint DEVPROP_TYPE_SECURITY_DESCRIPTOR = 0x00000013; // self-relative binary SECURITY_DESCRIPTOR

		internal const uint DEVPROP_TYPE_SECURITY_DESCRIPTOR_STRING = 0x00000014; // security descriptor string (SDDL format)

		internal const uint DEVPROP_TYPE_DEVPROPKEY = 0x00000015; // device property key (DEVPROPKEY)

		internal const uint DEVPROP_TYPE_DEVPROPTYPE = 0x00000016; // device property type (DEVPROPTYPE)

		internal const uint DEVPROP_TYPE_BINARY = DEVPROP_TYPE_BYTE | DEVPROP_TYPEMOD_ARRAY; // custom binary data

		internal const uint DEVPROP_TYPE_ERROR = 0x00000017; // 32-bit Win32 system error code

		internal const uint DEVPROP_TYPE_NTSTATUS = 0x00000018; // 32-bit NTSTATUS code

		internal const uint DEVPROP_TYPE_STRING_INDIRECT = 0x00000019; // string resource (@[path\]<dllname>,-<strId>)

		public static IEnumerable<Guid> GetClassPropertyKeys(DeviceInterface deviceInterface)
		{
			Guid classGuid = deviceInterface.ClassGuid;

			uint requiredPropertyKeyCount = 0;

			IntPtr pPropertyKeyArray = IntPtr.Zero;
			bool result = SetupDiGetClassPropertyKeys(ref classGuid, out pPropertyKeyArray, 0, out requiredPropertyKeyCount, DICLASSPROP_INTERFACE);

			if (result)
			{
			}

			return null;
		}

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiClassNameFromGuid", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiClassNameFromGuid(ref Guid classGuid, IntPtr className, uint classNameSize, ref uint requiredSize);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiClassNameFromGuid", CharSet = CharSet.Unicode, SetLastError = true)]
		internal static extern bool SetupDiClassNameFromGuid(ref Guid classGuid, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder className, uint classNameSize, ref uint requiredSize);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiDestroyDeviceInfoList", SetLastError = true)]
		internal static extern bool SetupDiDestroyDeviceInfoList(IntPtr deviceInfoList);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiEnumDeviceInfo", SetLastError = true)]
		internal static extern bool SetupDiEnumDeviceInfo(IntPtr deviceInfoSet, uint memberIndex, ref SP_DEVINFO_DATA deviceInfoData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiEnumDeviceInterfaces", SetLastError = true)]
		internal static extern bool SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref Guid interfaceClassGuid, uint memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiEnumDeviceInterfaces", SetLastError = true)]
		internal static extern bool SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, uint memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassDescription", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiGetClassDescription(ref Guid classGuid, IntPtr classDescription, uint classDescriptionSize, ref uint requiredSize);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassDescription", CharSet = CharSet.Unicode, SetLastError = true)]
		internal static extern bool SetupDiGetClassDescription(ref Guid classGuid, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder classDescription, uint classDescriptionSize, ref uint requiredSize);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassDevs", SetLastError = true)]
		internal static extern IntPtr SetupDiGetClassDevs(IntPtr classGuid, IntPtr enumerator, IntPtr hwnd, uint flags);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassDevs", SetLastError = true)]
		internal static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, IntPtr enumerator, IntPtr hwnd, uint flags);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassImageIndex", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiGetClassImageIndex(ref SP_CLASSIMAGELIST_DATA classImageListData, ref Guid classGuid, ref int imageIndex);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassImageList", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiGetClassImageList(ref SP_CLASSIMAGELIST_DATA classImageListData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetClassPropertyKeys")]
		internal static extern bool SetupDiGetClassPropertyKeys(ref Guid classGuid, out IntPtr pPropertyKeyArray, uint propertyKeyCount, out uint requiredPropertyKeyCount, uint flags);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetDeviceInterfaceDetail", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, 
			IntPtr deviceInterfaceDetailData, uint deviceInterfaceDataDetailDataSize, ref uint requiredSize, ref SP_DEVINFO_DATA deviceInfoData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetDeviceInterfaceDetail", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr deviceInfoSet, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, 
			ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, uint deviceInterfaceDataDetailDataSize, ref uint requiredSize, ref SP_DEVINFO_DATA deviceInfoData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetDeviceProperty", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiGetDeviceProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, ref DEVPROPKEY propertyKey, ref uint propertyType, IntPtr propertyBuffer, uint propertyBufferSize, ref uint requiredSize, uint flags);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiGetSelectedDevice", SetLastError = true)]
		internal static extern bool SetupDiGetSelectedDevice(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData);

		[DllImport("Setupapi.dll", EntryPoint = "SetupDiLoadClassIcon", CharSet = CharSet.Auto, SetLastError = true)]
		internal static extern bool SetupDiLoadClassIcon(ref Guid classGuid, out IntPtr largeIcon, ref int miniIconIndex);
	}

	public sealed class DeviceImageList
	{
		private readonly IDictionary<Guid, Icon> icons = new Dictionary<Guid, Icon>();

		private readonly ImageList imageList;

		public DeviceImageList(ImageList imageList)
		{
			if (imageList == null)
			{
				throw new ArgumentNullException("imageList");
			}

			this.imageList = imageList;
		}

		public Icon GetClassIcon(Guid classGuid)
		{
			if (this.icons.ContainsKey(classGuid))
			{
				return this.icons[classGuid];
			}

			IntPtr pIcon = DeviceInfoSet.GetClassIcon(classGuid);
			if (pIcon != IntPtr.Zero)
			{
				lock (this.imageList)
				{
					Icon icon = Icon.FromHandle(pIcon);
					this.icons[classGuid] = icon;
					this.imageList.Images.Add(classGuid.ToString("N"), icon);
					return icon;
				}
			}

			return null;
		}

		public int GetImage(Guid classGuid)
		{
			if (this.GetClassIcon(classGuid) != null)
			{
				return this.imageList.Images.IndexOfKey(classGuid.ToString("N"));
			}

			return -1;
		}
	}
}